package ee122Networking;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.lang.Object;

import cern.jet.random.Poisson;
import cern.jet.random.engine.DRand;

public class ClientSenderThread extends Thread {


	private Poisson poissonGenerator;
	private int routerPort;
	private int destPort;
	private String routerAddr;
	private FileInputStream gif = null;
	private int numPackets;
	public String name;
	public boolean isErrorChecking;
	
	private ArrayList<TwoHopPacket> allPackets = new ArrayList<TwoHopPacket>(1000);
	private DatagramSocket socket = null;
	private InetAddress address = null;
	private int numReceived = 0;
	

	
	
	
	public ClientSenderThread(String name, int interPacketTime, String routerAddr, int routerPort, int destPort, int numPackets ){
		poissonGenerator = new Poisson(interPacketTime, new DRand((int)System.currentTimeMillis()));

		this.name = name;
		this.routerPort = routerPort;
		this.routerAddr = routerAddr;
		this.numPackets = numPackets;
		this.destPort = destPort;
		
	}
	
	
	public ClientSenderThread(String name, int interPacketTime, String routerAddr, int routerPort, int destPort, String imgPath){
		// -1 since we don't know how many packets we want to send
		this(name, interPacketTime,routerAddr,routerPort,destPort, -1); 		
		try {
			gif = new FileInputStream(new File(imgPath));
		} catch (FileNotFoundException e) {
			System.err.println("Unable to open up the image at: " + imgPath);
			e.printStackTrace();
		}
		try {
			numPackets = (int) Math.ceil((double ) gif.available() / TwoHopPacket.MY_PACKET_DATA_LENGTH) ;
			System.out.println("Opened up "+imgPath+" successfully. It has " + gif.available() + " bytes to read and "+numPackets +" to send");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void run(){
		
		try {
			
			address = InetAddress.getByName(routerAddr);
			socket= new DatagramSocket();
			
			
			
		} catch (UnknownHostException e) {
			System.out.println(e + "==== cannot resolve "+routerAddr);
			System.exit(1);
		} catch (SocketException e) {
			System.out.println(e + "==== cannot get a socket");
			System.exit(1);
		}
		System.out.println("Client socket opened. Sending on port " + socket.getLocalPort());


		
		
		for(int i=0; i < numPackets; i++){
			
			TwoHopPacket myPacket = new TwoHopPacket(i, destPort, socket, numPackets);
			
		
			if(gif != null){
				byte[] imageData = getImageData(myPacket.getDataLength());
				myPacket.setData(imageData);
				
			}
			
			allPackets.add(myPacket);

			sendPacket(myPacket);
			
		}
		
		if (gif != null) {
			try {
				gif.close();
			} catch (IOException e) {
				System.err.println("Failed to close the gif");
				e.printStackTrace();
			}
		}
		
		
		try {
			socket.setSoTimeout(1000000);
		} catch (SocketException e) {
			System.out.println("cannot set timeout");
			e.printStackTrace();
		}
		
		
		while(true){
		
			byte[] buf = new byte[TwoHopPacket.MY_PACKET_LENGTH]; //TODO: get rid of hardcoded number
			DatagramPacket packet = new DatagramPacket(buf, buf.length);
			
			
			try {
				socket.receive(packet);
				numReceived ++;
				buf = packet.getData();
				TwoHopPacket requestPacket = new TwoHopPacket(buf);
				System.out.println("got a request packet for packet number "+requestPacket.getSequenceNumber());
				sendPacket(allPackets.get(requestPacket.getSequenceNumber()));
				System.out.println("packet  "+requestPacket.getSequenceNumber() + " resent");
			
			} 
			catch (SocketTimeoutException timeeeedout){
				System.out.println(name + " timed out, closing socket, stopping thread");
				break;
			}
			catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		socket.close();
		System.out.println("Number received " + numReceived );
				
	}

	
	private void sendPacket(TwoHopPacket myPacket) {
		
		DatagramPacket packet = new DatagramPacket(myPacket.getPacketByte(), myPacket.getPacketByte().length, address, routerPort);
		
		
		try {
			
			int next = poissonGenerator.nextInt();
			// then sleep
			long before = System.currentTimeMillis();
			Thread.sleep(next);
			long after = System.currentTimeMillis();
			// try to send the packet
			socket.send(packet);
			
			System.out.println(name+": slept for " + (after - before) + " ms");				
			
		} catch (IOException e) {
			System.out.println(e + "==== cannot send packet");
			System.exit(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
	}


	private byte[] getImageData(int length) {
		byte[] imgData = new byte[length];
		
		try {
			// reading in the imgData and print out how much was read
			System.out.println("Read in " + gif.read(imgData)+ " bytes of data");
		} catch (IOException e) {
			System.err.println("Failed to read data from the image files");
			e.printStackTrace();
		}
		return imgData;
	}
		
	
}
